class_name MediaWindow
extends VSplitContainer

# Minimum height of the media thumbnail as a percentage of screen height
const MEDIA_THUMBNAIL_MIN_HEIGHT_RATIO: float = 0.15

export(PackedScene) var MediaListItem_scene: PackedScene = load("res://src/scenes/media_list_item/media_list_item.tscn")
export(PackedScene) var ChannelCard_scene: PackedScene = load("res://src/scenes/channel_card/channel_card.tscn")
export(PackedScene) var Comments_scene: PackedScene = load("res://src/scenes/comments/comments.tscn")
export(PackedScene) var OpenWithNode_scene: PackedScene = load("res://src/scenes/open_with_node/open_with_node.tscn")
export(String, FILE) var default_thumbnail_path: String = "res://icon.png"

# URL to get the media through the LBRY protocol
var _lbry_url: String = ""
# URL of the channel on the LBRY blockchain
var _channel_url: String = ""
# All the resolved data for this `_lbry_url`
var _data: Dictionary = {}
# State this scene should be initialized with
var _init_state: Dictionary = {}
var _initialized: bool = false
var _async_helper := AsyncHelper.new(self)
onready var _main_region: MainRegion = get_parent()
onready var _default_thumbnail: Image = load(default_thumbnail_path).get_data()
onready var _comments_node: VBoxContainer = Comments_scene.instance()
onready var _title_node: RichTextLabel = find_node("Title")
onready var _description_node: RichTextLabel = find_node("Description")
onready var _information_node: RichTextLabel = find_node("Information")
onready var _channel_button: ButtonContainer = find_node("ChannelButton")
onready var _channel_card: ChannelCard = find_node("ChannelCard")
onready var _media_thumbnail_button: ButtonContainer = \
		find_node("MediaThumbnailButton")
onready var _media_thumbnail_node: TextureRect = find_node("MediaThumbnail")
onready var _media_thumbnail_spinner_node: Spinner = find_node("MediaSpinner")
onready var _media_download_button: ButtonContainer = \
		find_node("MediaDownloadButton")
onready var _media_open_with_button: ButtonContainer = \
		find_node("MediaOpenWithButton")
onready var _file_type_node: Label = find_node("FileTypeLabel")
onready var _expand_comments_button: Button = \
		find_node("ExpandCommentsButton")
onready var _collapse_comments_button: Button = \
		find_node("CollapseCommentsButton")
onready var _comments_scroll_container: ScrollContainer = \
		find_node("CommentsScrollContainer")
onready var _suggestions_node: DynamicGridContainer = find_node("Suggestions")


func init(data: Dictionary) -> void:
	if _initialized:
		return
	
	_data = data
	_lbry_url = data.get("short_url", "")
	_channel_url = data.get("signing_channel", {}).get("short_url", "")
	_initialized = true


func _ready() -> void:
	if not _init_state.empty():
		init(_init_state["_data"])
	
	_media_thumbnail_button.connect("pressed", self, "_open")
	_media_download_button.connect(
			"pressed", self, "_on_MediaDownloadButton_pressed")
	_media_open_with_button.connect("pressed", self,
			"_on_MediaOpenWithButton_pressed")
	_channel_button.connect(
			"pressed", self, "_open_channel_window", [_channel_url])
	_expand_comments_button.connect("pressed", self,
			"_on_ExpandCommentsButton_pressed")
	_collapse_comments_button.connect("pressed", self,
			"_on_CollapseCommentsButton_pressed")
	_comments_node.connect("channel_pressed", self, "_open_channel_window")
	
	# Set the split offset to initially only show the first child
	set_split_offset(int(OS.get_window_size().y / 2))
	_collapse_comments_button.hide()
	var flipped_texture := ImageTexture.new()
	var icon: Image = _collapse_comments_button.get_button_icon().get_data()
	icon.flip_y()
	flipped_texture.create_from_image(icon)
	_collapse_comments_button.set_button_icon(flipped_texture)
	
	_suggestions_node.set_item_size(MediaListItem.SIZE)
	
	var media_thumbnail_min_size := Vector2(
		_media_thumbnail_node.get_custom_minimum_size().x,
		OS.get_screen_size().y * MEDIA_THUMBNAIL_MIN_HEIGHT_RATIO
	)
	_media_thumbnail_node.set_custom_minimum_size(media_thumbnail_min_size)
	_media_thumbnail_spinner_node.set_custom_minimum_size(
			media_thumbnail_min_size)
	
	if _init_state.empty():
		_setup()
		_show_suggestions(yield(_get_suggestions(), "completed"))
	else:
		_setup_state()
		_show_suggestions(yield(_get_suggestions(), "completed"))


func get_tab_title() -> String:
	return _title_node.get_text()


func get_state() -> Dictionary:
	"""
	Returns:
		The data needed to restore the main changes made to this scene
	"""
	var state: Dictionary = {
		"_data": _data,
		"Comments_state": _comments_node.get_state(),
	}
	return state


func set_state(state: Dictionary) -> void:
	"""
	Call before the node enters the SceneTree to initialize it with the given
	state
	"""
	_init_state = state


func _setup() -> void:
	if len(_data) == 0:
		return
	
	var title: String = _data.get("value", {}).get("title", "")
	var description: String = _data.get("value", {}).get("description", "")
	var information: Dictionary = {}
	if _data.get("value", {}).get("release_time", "").is_valid_integer():
		information["release_timestamp"] = int(
				_data.get("value", {}).get("release_time"))
	var file_type: String = _data \
			.get("value", {}) \
			.get("stream_type", "unknown")
	
	_set_title(title)
	_set_description(description)
	_set_information(information)
	_set_file_type(file_type)
	
	_setup_media_thumbnail()
	_setup_channel_card()
	
	_comments_node.init(_data.get("claim_id", ""))
	_comments_scroll_container.add_child(_comments_node)


func _setup_state() -> void:
	""" Sets up the scene using the state description in `_init_state` """
	var title: String = _data.get("value", {}).get("title", "")
	var description: String = _data.get("value", {}).get("description", "")
	var information: Dictionary = {}
	if _data.get("value", {}).get("release_time", "").is_valid_integer():
		information["release_timestamp"] = int(
				_data.get("value", {}).get("release_time"))
	_set_title(title)
	_set_description(description)
	_set_information(information)
	
	_setup_media_thumbnail()
	_setup_channel_card()
	
	_comments_node.set_state(_init_state["Comments_state"])
	_comments_scroll_container.add_child(_comments_node)


func _set_title(title: String) -> void:
	_title_node.clear()
	_title_node.append_bbcode("[center]%s[/center]" % title)


func _set_description(description: String) -> void:
	_description_node.set_text(description)


func _set_media_thumbnail(thumbnail: ImageTexture) -> void:
	_media_thumbnail_node.set_texture(thumbnail)
	_media_thumbnail_node.show()
	_media_thumbnail_spinner_node.hide()


func _set_information(info: Dictionary) -> void:
	var text: String = ""
	if info.get("release_timestamp") != null:
		var datetime: Dictionary = Time.get_datetime_dict_from_unix_time(
				info["release_timestamp"])
		text += "Uploaded: %d %s %d %s:%s" % [
				datetime["day"],
				Helpers.month_to_string(datetime["month"]),
				datetime["year"],
				str(datetime["hour"]).pad_zeros(2),
				str(datetime["minute"]).pad_zeros(2),
		]
	_information_node.set_text(text)


func _set_file_type(type: String) -> void:
	_file_type_node.set_text("File type: " + type)


func _get_suggestions() -> Array:
	"""
	Finds media that is related to the current one by words in the title
	
	Must be called after `_setup()` has completed
	"""
	var media: Array = []
	var args: Dictionary = {
		"text": _title_node.get_text(),
		"any_tags": _data.get("value", {}).get("tags", []),
		"claim_type": "stream",
		"order_by": ["trending_score"],
		"no_totals": true,
		"remove_duplicates": true,
	}
	var permanent_search_options: Dictionary = Data.get_search_options()
	Helpers.dictionary_merge_into(args, permanent_search_options, true, false)
	var tracker: Dictionary = _async_helper.connect_wrapped(
			SDK.claim_search(args), "completed")
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	var search_response: Dictionary = tracker["result"]
	media = search_response.get("result", {}).get("items", [])
	
	for i in range(len(media) - 1, -1, -1):
		if media[i].get("short_url", "") == _lbry_url:
			media.remove(i)
			break
	return media


func _show_suggestions(suggestions: Array) -> void:
	for item in suggestions:
		var item_node: MediaListItem = MediaListItem_scene.instance()
		var channel_url: String = item \
				.get("signing_channel", {}) \
				.get("short_url", "")
		item_node.init(item)
		item_node.connect_media_button(
				"pressed", self, "_open_media_window", [item])
		item_node.connect_channel_button(
				"pressed", self, "_open_channel_window", [channel_url])
		_suggestions_node.add_child(item_node)


func _setup_media_thumbnail() -> void:
	var media_thumbnail := ImageTexture.new()
	var media_thumbnail_url: String = _data \
			.get("value", {}) \
			.get("thumbnail", {}) \
			.get("url", "")
	var tracker: Dictionary = _async_helper.connect_wrapped(
			Requests.get_image(media_thumbnail_url), "completed")
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	var media_thumbnail_img: Image = tracker["result"]
	if media_thumbnail_img.is_empty():
		media_thumbnail_img = _default_thumbnail
	media_thumbnail.create_from_image(media_thumbnail_img)
	
	_set_media_thumbnail(media_thumbnail)
	_media_thumbnail_node.set_tooltip(_lbry_url)


func _setup_channel_card() -> void:
	var channel_card: ChannelCard = ChannelCard_scene.instance()
	channel_card.init(_channel_url)
	_channel_card.get_parent().add_child_below_node(
			_channel_card, channel_card)
	_channel_card.queue_free()
	_channel_card = channel_card


func _open(media_opener: Dictionary={}) -> void:
	"""
	Opens the media with the appropriate program
	
	Args:
		media_opener: A valid media opener or an empty Dictionary (see
		`data.gd`)
	"""
	if _lbry_url.empty():
		return
	
	var mime_type: String = ""
	var streaming_url: String = ""
	var download_path: String = ""
	var download_completed: bool = false
	
	var args: Dictionary = {"uri": _lbry_url}
	var tracker: Dictionary = _async_helper.connect_wrapped(
			SDK.gets(args), "completed")
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	var get_response: Dictionary = tracker["result"]
	if not get_response.empty():
		mime_type = get_response.get("result", {}).get("mime_type", "")
		streaming_url = get_response.get("result", {}).get("streaming_url", "")
		# If the media hasn't been downloaded then the "download_path"
		# result will be set to `null`
		if get_response.get("result", {}).get("download_path") != null:
			download_path = \
					get_response.get("result", {}).get("download_path", "")
		download_completed = \
				get_response.get("result", {}).get("completed", false)
	else:
		var popup := AcceptDialog.new()
		popup.set_text("Failed to retrieve media")
		add_child(popup)
		popup.popup_centered()
		yield(popup, "popup_hide")
		popup.queue_free()
	
	if media_opener.empty():
		media_opener = Data.get_media_opener(mime_type)
	var command: String = media_opener["command"]
	var use_streaming: bool = media_opener["use_streaming"]
	
	if download_completed and not download_path.empty():
		# File is already downloaded, so open it
		MediaHandler.open_with(download_path, command, mime_type)
	elif use_streaming == false:
		# File is not downloaded but streaming shouldn't be used, so
		# download it then open it
		_show_download_popup("_download_then_open", [media_opener])
	elif not streaming_url.empty():
		# File is not downloaded but streaming can be used, so stream it
		MediaHandler.open_with(streaming_url, command, mime_type)


func _download(path: String) -> void:
	var tracker: Dictionary = _async_helper.connect_wrapped(
			DownloadManager.download(_lbry_url, path), "completed")
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	var download_started: bool = tracker["result"]
	
	# Display popup about the state of the download
	var msg: String = ""
	if download_started:
		tracker = _async_helper.connect_wrapped(
				DownloadManager.get_status(_lbry_url), "completed")
		yield(_async_helper.wait_until_finished([tracker]), "completed")
		var download_status: Dictionary = tracker["result"]
		var download_to: String = download_status.get("download_path", path)
		if download_status.get("completed") == true:
			msg = "Downloaded to:\n%s" % download_to
		else:
			msg = "Downloading to:\n%s" % download_to
	else:
		msg = "Failed to start the download"
	var accept_dialogue := AcceptDialog.new()
	accept_dialogue.set_text(msg)
	add_child(accept_dialogue)
	accept_dialogue.popup_centered()
	yield(accept_dialogue, "popup_hide")
	accept_dialogue.queue_free()


func _download_then_open(path: String, media_opener: Dictionary) -> void:
	var tracker: Dictionary = _async_helper.connect_wrapped(
			_download(path) as Object, "completed", false)
	yield(_async_helper.wait_until_finished([tracker]), "completed")
	
	var command: String = media_opener["command"]
	DownloadManager.set_open_when_downloaded(_lbry_url, true, command)


func _show_download_popup(file_selected_callback: String, args: Array=[]
		) -> void:
	var file_dialog := FileDialog.new()
	file_dialog.connect("file_selected", self, file_selected_callback, args)
	file_dialog.set_access(FileDialog.ACCESS_FILESYSTEM)
	file_dialog.set_exclusive(true)
	file_dialog.set_resizable(true)
	add_child(file_dialog)
	file_dialog.popup_centered_minsize(Vector2(100, 200))
	yield(file_dialog, "popup_hide")
	file_dialog.queue_free()


func _open_media_window(data: Dictionary) -> void:
	_main_region.set_window("MediaWindow", "init", [data])


func _open_channel_window(channel_url: String) -> void:
	_main_region.set_window("ChannelWindow", "init", [channel_url])


func _on_MediaDownloadButton_pressed() -> void:
	_show_download_popup("_download")


func _on_MediaOpenWithButton_pressed() -> void:
	var mime_type: String = _data \
			.get("value", {}) \
			.get("source", {}) \
			.get("media_type", "")
	
	if not mime_type.empty():
		var open_with_node: OpenWithNode = OpenWithNode_scene.instance()
		open_with_node.init(mime_type)
		open_with_node.connect("confirmed", self, "_on_OpenWithNode_confirmed",
				[open_with_node])
		add_child(open_with_node)
		open_with_node.popup_centered_ratio(0.3)
		yield(open_with_node, "popup_hide")
		open_with_node.queue_free()
	else:
		push_warning("MIME type is unknown")


func _on_ExpandCommentsButton_pressed() -> void:
	_expand_comments_button.hide()
	_collapse_comments_button.show()
	set_split_offset(int(-OS.get_window_size().y / 2))


func _on_CollapseCommentsButton_pressed() -> void:
	_collapse_comments_button.hide()
	_expand_comments_button.show()
	set_split_offset(int(OS.get_window_size().y / 2))


func _on_OpenWithNode_confirmed(open_with_node: OpenWithNode) -> void:
	var media_opener: Dictionary = {}
	media_opener["command"] = open_with_node.get_command()
	media_opener["use_streaming"] = open_with_node.get_use_streaming()
	var always_use: bool = open_with_node.get_always_use()
	
	if always_use:
		# Save the command to use for the media's MIME type in the future
		var mime_type: String = _data \
				.get("value", {}) \
				.get("source", {}) \
				.get("media_type", "")
		if not mime_type.empty():
			Data.save_media_opener(mime_type, media_opener)
	
	_open(media_opener)
